﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace RCloud.Windows
{
    public class Model
    {
        /// <summary>
        /// 商品集合
        /// </summary>
        Dictionary<long, ProductInfo> Products = new Dictionary<long, ProductInfo>();

        /// <summary>
        /// 用户集合
        /// </summary>
        Dictionary<long, UserInfo> Users = new Dictionary<long, UserInfo>();

        /// <summary>
        /// 总特征集合
        /// </summary>
        Dictionary<long, TagInfo> Tags = new Dictionary<long, TagInfo>();

        /// <summary>
        /// 加载所有商品
        /// </summary>
        /// <param name="movies"></param>
        public void LoadProducts(RCloud.Data.Movie[] movies)
        {
            foreach (var m in movies)
            {
                ProductInfo pi = new ProductInfo { TagSet = new TagSet() };
                Products.Add(m.ID, pi);
            }
        }

        long LastTagID = 0;

        ProductInfo InitProduct(long productid)
        {
            ProductInfo pi = new ProductInfo();
            pi.TagSet.InitTag(LastTagID--);
            Products.Add(productid, pi);
            return pi;
        }

        /// <summary>
        /// 加载商品的类别
        /// </summary>
        /// <param name="genres"></param>
        public void LoadProductGenres(RCloud.Data.MovieGenres[] genres)
        {
            ProductInfo pi;
            foreach (var g in genres)
            {
                if (!Products.TryGetValue(g.MovieID, out pi))
                {
                    pi = InitProduct(g.MovieID);
                }
                pi.TagSet.InitTag(g.TagID);
            }
        }

        /// <summary>
        /// 加载商品的标签
        /// </summary>
        /// <param name="tags"></param>
        public void LoadProductTags(RCloud.Data.MovieTags[] tags)
        {
            ProductInfo pi;
            foreach (var g in tags)
            {
                if (!Products.TryGetValue(g.MovieID, out pi))
                {
                    pi = InitProduct(g.MovieID);
                }
                pi.TagSet.InitTag(g.TagID);
            }
        }

        /// <summary>
        /// 处理投票数据
        /// </summary>
        /// <param name="rates"></param>
        public void ProcessRate(IEnumerable<RCloud.Data.UserRatingHistoryLite> rates)
        {
            ProductInfo pi;
            UserInfo ui;
            foreach (var r in rates)
            {
                pi = Products[r.MovieID];
                if (!Users.TryGetValue(r.UserID, out ui))
                {
                    ui = new UserInfo();
                    Users.Add(r.UserID, ui);
                }

                pi.BeRate((int)r.Rating, ui);

                ui.RateP((int)r.Rating, pi, Tags);
            }
        }

        /// <summary>
        /// 方法一：采用平均值
        /// </summary>
        /// <param name="userid"></param>
        /// <param name="productid"></param>
        /// <returns></returns>
        int ForestRatingMethod1(long userid, long productid)
        {
            ProductInfo pi = Products[productid];
            UserInfo ui;
            if (!Users.TryGetValue(userid, out ui))
            {
                //新用户怎么办？
                return -1;
            }
            else
            {
                //老用户就直接比较

                #region 老评分
                //从平均评分开始，对双方有特点的特征开始进行比较
                //目前阶段，商品还没有讨厌的特征，所以只需比较两次
                //1.用户喜欢的特征有无
                //2.用户讨厌的特征有无

                /*
                var pitags = pi.TagSet.LikeTags;
                int offset = 0;
                int match = 0;

                //执行1
                foreach (var p in ui.LikeTags)
                {
                    if (pitags.Contains(p))
                    {
                        match++;
                        offset += p.Level;
                    }
                }

                //执行2
                foreach (var h in ui.HateTags)
                {
                    if (pitags.Contains(h))
                    {
                        match++;
                        offset += h.Level;
                    }
                }
                
                int realrating = ui.AvgRating + offset;
                if (realrating > 10) realrating = 10;
                else if (realrating < 1) realrating = 1;
                */
                #endregion

                var pt = pi.TagSet;
                int match = 0;
                double offset = 0;
                foreach (var p in ui.TagSet)
                {
                    if (pt.ContainsKey(p.Key))
                    {
                        match++;
                        offset += p.Value.AvgScore;
                    }
                }

                int realrating = ui.AvgRating;
                if (match > 0) realrating += (int)Math.Round(offset / match);
                //if (realrating % 2 == 1) realrating++;

                if (realrating > 10) realrating = 10;

                //throw new Exception();

                return realrating;
            }
        }

        #region 预测方法二（用户相似度?）

        /// <summary>
        /// 相似度词典缓存
        /// </summary>
        Dictionary<string, double> XSD = new Dictionary<string, double>();

        /// <summary>
        /// 获取两个用户的相似度
        /// </summary>
        /// <param name="user1"></param>
        /// <param name="user2"></param>
        /// <returns></returns>
        double GetXSD(long user1, long user2)
        {
            long mu = user1, mu2 = user2;
            if (mu < mu2) { mu = mu2; mu2 = user1; }
            string key = mu.ToString() + '_' + mu2.ToString();
            double xsd = 0;
            if (!XSD.TryGetValue(key, out xsd))
            {
                if (!Users.ContainsKey(mu) || !Users.ContainsKey(mu2)) xsd = 0d;
                else
                {
                    //对比两者的特征集，计算重合特征乘积之和
                    var u1t = Users[mu].TagSet;
                    var u2t = Users[mu2].TagSet;
                    foreach (var t1 in u1t)
                    {
                        if (t1.Value.AvgScore == 0) continue;
                        if (!u2t.ContainsKey(t1.Key)) continue;
                        xsd += t1.Value.AvgScore * u2t[t1.Key].AvgScore;
                    }
                }
                XSD.Add(key, xsd);
            }
            return xsd;
        }

        public IEnumerable<RCloud.Data.UserRatingHistoryLite> RatesForForest { get; set; }

        Dictionary<long, long[]> SameProductUsers = new Dictionary<long, long[]>();

        long[] GetSameProductUsers(long productid)
        {
            long[] users;
            if (!SameProductUsers.TryGetValue(productid, out users))
            {
                users = RatesForForest.Where(r => r.MovieID == productid).Select(r => r.UserID).Distinct().ToArray();
                SameProductUsers.Add(productid, users);
            }
            return users;
        }

        /// <summary>
        /// 预测返回
        /// </summary>
        public struct ForestRatingReturn
        {
            /// <summary>
            /// 评分数
            /// </summary>
            public int Rating;
            /// <summary>
            /// 是否是新用户
            /// </summary>
            public bool IsNewUser;
        }

        /// <summary>
        /// 第三种预测方法：用户特征-影片特征-全局特征
        /// </summary>
        /// <param name="userid"></param>
        /// <param name="productid"></param>
        /// <returns></returns>
        ForestRatingReturn ForestRatingMethod3(long userid, long productid)
        {
            ProductInfo pi = Products[productid];
            UserInfo ui;
            ForestRatingReturn rt;

            if (!Users.TryGetValue(userid, out ui))
            {
                //新用户怎么办？
                //返回电影的平均评分
                rt.IsNewUser = true;

                rt.Rating = pi.AvgRating;
                return rt;
            }
            else
            {
                //老用户
                rt.IsNewUser = false;

                //以平均评分为基准

                rt.Rating = 0;
                //rt.Rating = realrating;
                return rt;
            }
        }

        ForestRatingReturn ForestRatingMethod2(long userid, long productid)
        {
            ProductInfo pi = Products[productid];
            UserInfo ui;
            ForestRatingReturn rt;

            if (!Users.TryGetValue(userid, out ui))
            {
                //新用户怎么办？
                //返回电影的平均评分
                rt.IsNewUser = true;

                rt.Rating = pi.AvgRating;
                return rt;
            }
            else
            {
                //老用户就直接比较
                rt.IsNewUser = false;

                //查出同样对这部电影评价过的用户名单
                var samemovierates = GetSameProductUsers(productid);
                //对每个用户查询相似度
                double maxxsd = 0;
                long maxuid = 0;
                foreach (var uid in samemovierates)
                {
                    if (userid == uid) continue;
                    double xsd = GetXSD(userid, uid);
                    if (xsd > maxxsd) { maxxsd = xsd; maxuid = uid; }
                }
                //没有人...
                if (maxuid == 0)
                {
                    rt.Rating = ui.AvgRating;
                    return rt;
                }

                //已经查到最佳相似度
                int targetAvg = Users[maxuid].AvgRating;
                long targetScore = RatesForForest.Where(r => r.UserID == maxuid && r.MovieID == productid).First().Rating;

                int realrating = (int)(ui.AvgRating + targetScore - targetAvg);

                if (realrating > 10) realrating = 10;

                //throw new Exception();

                rt.Rating = realrating;
                return rt;
            }
        }

        #endregion

        /// <summary>
        /// 预测评分
        /// </summary>
        /// <param name="userid"></param>
        /// <param name="productid"></param>
        /// <returns></returns>
        public ForestRatingReturn ForestRating(long userid, long productid)
        {
            //return ForestRatingMethod1(userid, productid);
            return ForestRatingMethod2(userid, productid);
        }
    }

    #region 标签信息

    /// <summary>
    /// 一个标签信息
    /// </summary>
    public struct TagInfo
    {
        public TagInfo(long tagID)
        {
            _avgScore = -1;
            TagID = tagID;
            Count = 0;
            SumScore = 0;
            PrevSumScore = 0;
        }

        /// <summary>
        /// 标签ID
        /// </summary>
        public long TagID;
        /// <summary>
        /// 出现的次数
        /// </summary>
        public int Count;
        /// <summary>
        /// 总评分
        /// </summary>
        public double SumScore;
        /// <summary>
        /// 上次时间点总评分
        /// </summary>
        public double PrevSumScore;

        private double _avgScore;

        /// <summary>
        /// 平均评分
        /// </summary>
        public double AvgScore
        {
            get
            {
                if (_avgScore == -1) _avgScore = Count == 0 ? 0 : (int)(SumScore / Count);
                return _avgScore;
            }
        }

        /// <summary>
        /// 增加一个分数
        /// </summary>
        /// <param name="score"></param>
        public void AddScore(double score)
        {
            SumScore += score;
            Count++;
            _avgScore = -1;
        }

        public override int GetHashCode()
        {
            return TagID.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            return ((TagInfo)obj).TagID == TagID;
        }
    }

    #endregion

    public struct TagLevel : IComparable
    {
        /// <summary>
        /// 标签ID
        /// </summary>
        public long TagID;

        public int Level;

        public override int GetHashCode()
        {
            return TagID.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            return ((TagLevel)obj).TagID == TagID;
        }

        #region IComparable 成员

        public int CompareTo(object obj)
        {
            return Level - ((TagLevel)obj).Level;
        }

        #endregion
    }


    /// <summary>
    /// 标签集，使用两个标签集计算推荐度
    /// </summary>
    public class TagSet : Dictionary<long, TagInfo>
    {
        /// <summary>
        /// 能对用户产生正面影响的标签
        /// </summary>
        public SortedSet<TagLevel> LikeTags = new SortedSet<TagLevel>();
        /// <summary>
        /// 能对用户产生负面影响的标签
        /// </summary>
        public SortedSet<TagLevel> HateTags = new SortedSet<TagLevel>();
        /// <summary>
        /// 目前为止的最大分值
        /// </summary>
        public int MaxScore;
        /// <summary>
        /// 目前为止的最小分值
        /// </summary>
        public int MinScore;

        /// <summary>
        /// 初始化特征
        /// </summary>
        /// <param name="tagID"></param>
        public void InitTag(long tagID)
        {
            if (ContainsKey(tagID)) return;
            Add(tagID, new TagInfo(tagID) { SumScore = 10000, Count = 1 });
        }

        /// <summary>
        /// 调整
        /// </summary>
        /// <param name="set">用来调整</param>
        /// <param name="rating">分数</param>
        public void Rate(TagSet set, int rating)
        {

        }

        /// <summary>
        /// 为某个标签评分，传入的评分应该是已经修正过的
        /// </summary>
        /// <param name="tagid"></param>
        /// <param name="rating">评分，不要传入0值以优化性能</param>
        public void Rate(long tagid, int rating)
        {
            /*Debug.Assert(rating != 0, "不能传入0值");
            //1.分数变化
            TagInfo info;
            int oldscore = 0;
            if (!TryGetValue(tagid, out info))
            {
                info.TagID = tagid;
                info.Count = 1;
                info.SumScore = rating;
                Add(tagid, info);
            }
            else
            {
                info.Count++;
                oldscore = info.SumScore;
                info.SumScore += rating;
                this[tagid] = info;
            }
            //2.各种判断，看是否进入或离开有影响力的圈子
            //有影响力的意思是用户会因为这个标签的存在对这个商品的感兴趣程度变化
            //影响力因子计算：第一部分，点击率：a=Count/TotalCount，TotalCount是TagSet的属性
            //第二部分，评分：b=SumScore/MaxScore或MinScore
            //预测评分的算法是综合考虑各个特征的影响力因子，从而得到最终的预测分值（神经网络？）
            //设置满分阈值，超过此阈值即表示评分为满分
            //预测评分：该用户平均评分加各影响力因子
            */
        }
    }

    /// <summary>
    /// 商品
    /// </summary>
    public class ProductInfo
    {
        /// <summary>
        /// 商品的特征集
        /// </summary>
        public TagSet TagSet = new TagSet();

        private int _avgRate = 5;
        /// <summary>
        /// 平均评分
        /// </summary>
        public int AvgRating
        {
            get
            {
                if (_avgRate == -1)
                {
                    _avgRate = RateCount == 0 ? 0 : RateScore / RateCount;
                }
                return _avgRate;
            }
        }
        /// <summary>
        /// 总分
        /// </summary>
        public int RateScore = 0;
        /// <summary>
        /// 反馈次数
        /// </summary>
        public int RateCount = 0;

        public void BeRate(int rating, UserInfo ui)
        {
            RateCount++;
            RateScore += rating;
            _avgRate = -1;
        }
    }

    /// <summary>
    /// 用户
    /// </summary>
    public class UserInfo
    {
        /// <summary>
        /// 平均评分，用户的特殊属性，用于修正其评分以判断用户对商品的喜好程度及预测评分。
        /// </summary>
        public int AvgRating = 5;

        /// <summary>
        /// 投票计数
        /// </summary>
        public int RateCount = 0;

        /// <summary>
        /// 投票总分
        /// </summary>
        public int TotalRate = 0;

        /// <summary>
        /// 用户的特征集
        /// </summary>
        TagSet _tagSet = new TagSet();

        public TagSet TagSet
        {
            get { return _tagSet; }
        }

        /// <summary>
        /// 用户的喜爱特征集
        /// </summary>
        public SortedSet<TagLevel> LikeTags
        {
            get
            {
                return _tagSet.LikeTags;
            }
        }

        /// <summary>
        /// 用户的讨厌特征集
        /// </summary>
        public SortedSet<TagLevel> HateTags
        {
            get
            {
                return _tagSet.HateTags;
            }
        }


        /// <summary>
        /// 为某个商品评分，传入的是1-10的分值
        /// </summary>
        /// <param name="rating"></param>
        public void RateP(int rating, ProductInfo product, Dictionary<long, TagInfo> globalTags)
        {
            TotalRate += rating;
            RateCount++;
            if (RateCount > 10)
            {
                AvgRating = TotalRate / RateCount;
            }

            //对自己的标签进行修正
            rating = rating - AvgRating;

            //此时分数成为-10~10的数。
            //1.处理LikeTags
            /*processTagSet(product.TagSet.LikeTags, rating);
            processTagSet(product.TagSet.HateTags, -rating);*/

            //1.计算匹配特征权值与实际评分的平均偏差
            double offset = 0;
            int match = product.TagSet.Count;
            Dictionary<long, TagInfo> needProcess = new Dictionary<long, TagInfo>();
            foreach (var entry in product.TagSet)
            {
                TagInfo info;
                if (_tagSet.TryGetValue(entry.Key, out info))
                {
                    offset += info.AvgScore - rating;
                    needProcess.Add(entry.Key, info);
                }
                else
                {
                    offset -= rating;
                    info = new TagInfo(entry.Key);
                    _tagSet.Add(entry.Key, info);
                    needProcess.Add(entry.Key, info);
                }
            }
            double avgOffset = match == 0 ? 0d : (offset / match);

            //2.处理各个标签
            foreach (var entry in needProcess)
            {
                var info = entry.Value;
                //修改
                info.AddScore(info.AvgScore - avgOffset);
                this._tagSet[entry.Key] = info;
            }

            //处理全局标签
            foreach (var entry in needProcess)
            {
                TagInfo gt;
                if (!globalTags.TryGetValue(entry.Key, out gt))
                {
                    //没有就新增
                    gt.TagID = entry.Key;
                    gt.AddScore(entry.Value.AvgScore - avgOffset);
                    globalTags.Add(entry.Key, gt);
                }
                else
                {
                    gt.AddScore(entry.Value.AvgScore - avgOffset);
                    globalTags[entry.Key] = gt;
                }
            }

        }
    }
}
